//
//  CKCache.m
//  ConnectionKit
//
//  Created by Matthias Plappert on 30.06.09.
//  Copyright 2009 phapswebsolutions. All rights reserved.
//

#import "CKCache.h"

@implementation CKCache

static CKCache *sharedCache = nil;
static NSString *_path = nil;

#pragma mark -
#pragma mark Singleton

+ (void)initialize
{
	if (self == [CKCache class]) {
		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
		NSString *documentsDirectory = [paths objectAtIndex:0];
		_path = [[documentsDirectory stringByAppendingPathComponent:@"/CKCache/"] copy];
	}
}

+ (CKCache *)sharedCache
{
    @synchronized (self) {
        if (sharedCache == nil) {
            sharedCache = [[self alloc] init];
        }
    }
    return sharedCache;
}

+ (id)allocWithZone:(NSZone *)zone
{
    @synchronized (self) {
        if (sharedCache == nil) {
            sharedCache = [super allocWithZone:zone];
            return sharedCache;
        }
    }
    return nil;
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain
{
    return self;
}

- (unsigned)retainCount
{
    return UINT_MAX;
}

- (void)release
{
    // do nothing
}

- (id)autorelease
{
    return self;
}

#pragma mark -
#pragma mark Methods

+ (void)terminateOperationsWithMaximumCacheSize:(unsigned long long)maxSize;
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	
	// Check cache size and remove all files if it is too big
	NSArray *contents = [fileManager directoryContentsAtPath:_path];
	unsigned long long overallSize = 0; // in bytes
	
	for (int i = 0; i < [contents count]; i++) {
		NSString *file = [contents objectAtIndex:i];
		NSString *full = [_path stringByAppendingPathComponent:file];
		if ([file characterAtIndex:0] != '.') {
			// Ignore hidden - and therefor system - files
			overallSize = overallSize + [[[fileManager attributesOfItemAtPath:full error:nil] objectForKey:NSFileSize] unsignedLongLongValue];
		}
	}
	
	#ifdef DEBUG
		NSLog(@"Overall cache size is %llu bytes", overallSize);
	#endif
	
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	if (overallSize > maxSize) {
		#ifdef DEBUG
			NSLog(@"Cache is too big -> deleting...");
		#endif
		
		// Bigger than max size -> delete cached files
		for (int i = 0; i < [contents count]; i++) {
			NSString *file = [contents objectAtIndex:i];
			NSString *full = [_path stringByAppendingPathComponent:file];
			if ([fileManager fileExistsAtPath:full]) {
				[fileManager removeItemAtPath:full error:nil];
			}
		}
		[defaults setObject:[NSNumber numberWithUnsignedLongLong:0] forKey:kCKCacheOverallSize];
	} else {
		[defaults setObject:[NSNumber numberWithUnsignedLongLong:overallSize] forKey:kCKCacheOverallSize];
	}
}

- (id)init
{
	if (self = [super init]) {
		_data = [[NSMutableDictionary alloc] init];
		
		// Try to create the required file structure
		NSFileManager *fileManager = [NSFileManager defaultManager];
		if (![fileManager fileExistsAtPath:_path]) {
			#ifdef DEBUG
				NSLog(@"creating directory for CKCache at %@...", _path);
			#endif
			if ([fileManager createDirectoryAtPath:_path attributes:nil] == NO) {
				#ifdef DEBUG
				    NSLog(@"... failure!");
				#endif
			} else {
				#ifdef DEBUG
					NSLog(@"... success!");
				#endif
			}
		}
		
		// Register for memory warning
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(didReceiveMemoryWarning)
													 name:UIApplicationDidReceiveMemoryWarningNotification
												   object:nil];
	}
	return self;
}

- (void)dealloc
{
	// Unregister for memory warnings
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[_data release];
	[_path release];
	
	[super dealloc];
}

#pragma mark -
#pragma mark Internal handling

- (BOOL)saveData:(NSData *)data forIdentifier:(NSString *)identifier
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSString *file = [[_path stringByAppendingPathComponent:identifier] stringByAppendingPathExtension:@"cache"];
	
	#ifdef DEBUG
		NSLog(@"saving data in cache-file %@...", file);
	#endif
	
	if ([fileManager fileExistsAtPath:file]) {
		if ([fileManager removeItemAtPath:file error:nil] == NO) {
			return NO;
		}
	}
	BOOL result = [fileManager createFileAtPath:file contents:data attributes:nil];
	if (result == NO) {
		#ifdef DEBUG
			NSLog(@"... failure!", file);
		#endif
	} else {
		#ifdef DEBUG
			NSLog(@"... success!", file);
		#endif
	}
	return result;
}

- (BOOL)loadDataForIdentifier:(NSString *)identifier
{
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSString *file = [[_path stringByAppendingPathComponent:identifier] stringByAppendingPathExtension:@"cache"];
	if ([fileManager fileExistsAtPath:file]) {
		NSData *data = [[NSData alloc] initWithContentsOfMappedFile:file];
		if (data != nil) {
			[_data setObject:data forKey:identifier];
			[data release];
			
			return YES;
		} else {
			[data release];
			
			return NO;
		}
	} else {
		return NO;
	}
}

- (void)didReceiveMemoryWarning
{
	[_data removeAllObjects];
}

#pragma mark -
#pragma mark Accessors

- (void)setData:(NSData *)data forIdentifier:(NSString *)identifier
{	
	if (data != nil) {
		[_data setObject:[NSData dataWithData:data] forKey:identifier];
		[self saveData:data forIdentifier:identifier];
	}
}

- (NSData *)dataForIdentifier:(NSString *)identifier
{
	NSData *data = [_data objectForKey:identifier];
	if (data == nil) {
		if ([self loadDataForIdentifier:identifier] == YES) {
			return [NSData dataWithData:[_data objectForKey:identifier]];
		} else {
			return nil;
		}
	} else {
		return [NSData dataWithData:data];
	}
}

@end
